package myredispool

import (
	"errors"
	"strconv"
	"sync"

	"github.com/garyburd/redigo/redis"
)

type PoolConf struct {
	Net       string
	IpAddress string
	MaxConn   int32
	MinConn   int32

	idleConn       int32
	createdConnNum int32
	pool           *sync.Pool

	mu    sync.Mutex
	once  sync.Once
	isErr bool
}

func (p *PoolConf) createdConnSub() {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.createdConnNum--
	p.isCreatedLessThanMinconn()
}

func (p *PoolConf) poolInit() {
	p.pool = &sync.Pool{New: p.newFunc}

	var i int32
	for i = 0; i < p.MaxConn; i++ {
		c := p.pool.Get()
		switch v := c.(type) {
		case *redis.Conn:
			if *v == nil {
				p.isErr = true
				return
			}
		}
		p.pool.Put(c)
		p.idleConn++
		p.createdConnNum++
	}
}

func (p *PoolConf) Init() error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.Net == "" || p.IpAddress == "" || p.MaxConn <= 0 || p.MinConn < 0 || p.MaxConn < p.MinConn {
		return errors.New("The argument of the Init() is error")
	}
	p.once.Do(p.poolInit)
	if p.isErr {
		return errors.New("get conn error in Init()")
	}
	return nil
}

func (p *PoolConf) newFunc() interface{} {
	c, _ := redis.Dial(p.Net, p.IpAddress)
	// 这里为什么要返回一个接口的地址，你就返回这个接口有问题吗？
	// 为什么要忽略掉出错的可能？
	/*
	  这样写是不是好一点？
	  c, err := redis.Dial(p.Net, p.IpAddress)
	  if err != nil {
	      return err
	  }
	  return c
	*/
	return &c
}

// 说了多次，你这个函数的名字和在里面干的事情不一致
func (p *PoolConf) isCreatedLessThanMinconn() {
	if p.createdConnNum < p.MinConn {
		for i := p.createdConnNum; i <= p.MinConn; i++ {
			// 这个Get会返回nil吗？
			c := p.pool.Get()
			p.pool.Put(c)
			p.idleConn++
			p.createdConnNum++
		}
	}
}

func (p *PoolConf) getConn() redis.Conn {
	p.mu.Lock()
	defer p.mu.Unlock()

	var c interface{}
	//判断是否剩余的连接
	if p.idleConn > 0 {
		p.idleConn--
		c = p.pool.Get()
	} else {
		//无剩余连接就看已经创建的数量是否达到最大值
		if p.createdConnNum >= p.MaxConn {
			return nil
		} else {
			//没有达到最大连接的时候
			p.createdConnNum++
			c = p.pool.Get()
		}
	}
	//取出连接时看一下是否是空的连接要是空的就从已创建的连接里删掉然后返回一个error
	switch v := c.(type) {
	case *redis.Conn:
		if (*v) == nil {
			p.createdConnNum--
			//删掉一个已创建的连接后，判断createdConn是否小于minConn
			p.isCreatedLessThanMinconn()
			return nil
		}
		return *v
	}
	return nil
} //这样一系列的操作之后就保证了外面调用的函数不用考虑很多，只去取连接就可以

func (p *PoolConf) putConn(c *redis.Conn) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.idleConn++
	p.pool.Put(c)
}

func (p *PoolConf) SetUserFreq(userId, offerId, count string) error {
	if userId == "" || offerId == "" || count == "" {
		return errors.New("The argument of the SetUserFreq(string,string,string) is error")
	}
	c := p.getConn()
	if c == nil {
		return errors.New("getConn() error in SetUserFreq()")
	}

	// pipeline的优势是什么？如果只需要一次send，有必要先send，再flush吗？这样做增加了复杂性
	err := c.Send("HINCRBY", userId, offerId, count)
	if err != nil {
		c.Close()
		p.createdConnSub()
		return err
	}
	ferr := c.Flush()
	if ferr != nil {
		c.Close()
		p.createdConnSub()
		return ferr
	}
	p.putConn(&c)
	return nil
}

// PoolConf这个对象的名字就是连接池配置的意思，跟设置用户频次有什么关系？
func (p *PoolConf) GetUserFreq(userId string) (map[string]int, error) {
	if userId == "" {
		return nil, errors.New("The argument of the GetUserFreq() is error")
	}

	c := p.getConn()
	if c == nil {
		return nil, errors.New("getConn error in SetUserFreq()")
	}
	params, err := redis.Strings(c.Do("HGETALL", userId))
	if err != nil {
		c.Close()
		p.createdConnSub()
		return nil, errors.New("The connection errors")
	}
	p.putConn(&c)
	var infomap map[string]int
	infomap = make(map[string]int)
	for i := 0; i < len(params); i += 2 {
		count, err := strconv.Atoi(params[i+1])
		if err != nil {
			return nil, err
		}
		infomap[params[i]] = count
	}
	return infomap, err
}
